/*
* Copyright (C) Microsoft. All rights reserved.  
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not 
* use this file except in compliance with the License.  You may obtain a copy 
* of the License at http://www.apache.org/licenses/LICENSE-2.0  
* 
* THIS CODE IS PROVIDED *AS IS* BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, EITHER EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION ANY IMPLIED 
* WARRANTIES OR CONDITIONS OF TITLE, FITNESS FOR A PARTICULAR PURPOSE, 
* MERCHANTABLITY OR NON-INFRINGEMENT. 
*
* See the Apache License, Version 2.0 for specific language governing 
* permissions and limitations under the License.
*
*/
#pragma once

#include <baja/common.hpp>
#include <baja/graphics.hpp>
#include <baja/graphics/scenes.hpp>
#include <baja/math.hpp>
#include 
namespace baja { namespace app {

namespace g = baja::graphics;
namespace gs = baja::graphics::scenes;
namespace m = baja::math;

class scene_references
{
private:
    scene_references() {}

public:
    friend class std::_Ref_count_obj<scene_references>;
    static std::shared_ptr<scene_references> create(
        const std::shared_ptr<icamera>& camera,
        const wchar* fontName,
        const int fontSize
        )
    {
        std::shared_ptr<scene_references> returnValue = 
            std::make_shared<scene_references>();
        returnValue->initialize(camera, fontName, fontSize);
        return returnValue;
    }

    void renderFPS()
    {
        std::shared_ptr<g::irender_service> renderService = _renderService.lock();
        std::shared_ptr<g::iengine> graphics = _graphics.lock();

        boost::wformat fmt = boost::wformat(L"FPS: %0.2f") % renderService->getFramesPerSecond();
        std::wstring ws = fmt.str();

        g::viewport vp;
        graphics->getViewport(&vp);

        // measure the text's rect
        m::rectangle textRect;
        _font->measureText(
            ws.c_str(),
            g::horizontal_alignment::right,
            g::vertical_alignment::top,
            false,
            &textRect
            );

        textRect.top += 12;
        textRect.bottom += 12;
        textRect.left += vp.width - 10;
        textRect.right += vp.width - 10;

        // draw backdrop rectangle
        m::rectangle backdropRect(textRect);
        backdropRect.top -= 3;
        backdropRect.bottom += 1;
        backdropRect.left -= 4;
        backdropRect.right += 2;
            
        std::shared_ptr<g::ieffect> effect;
        g::handle technique;
        effect = graphics->getStandardEffect(g::standard_effect::screenColored, &technique);

        graphics->setZBuffer(g::enabled_state::disabled);
        graphics->setCulling(g::cull_state::none);

        uint32 passes = 0;
        effect->setTechnique(technique);
        passes = effect->begin(graphics);
        for(uint32 p = 0; p < passes; p++)
        {
            effect->beginPass(p);

            graphics->beginScreenSpace(g::primitive_type::triangleStrip);

            graphics->setColor(g::packColor(128, 0, 0, 0));

            m::point4d pos1(backdropRect.left-0.5f, backdropRect.top-0.5f, 1.0f);
            graphics->setScreenSpaceVertex(pos1);

            m::point4d pos2(backdropRect.right-0.5f, backdropRect.top-0.5f, 1.0f);
            graphics->setScreenSpaceVertex(pos2);

            graphics->setColor(g::packColor(128, 0, 0, 0));

            m::point4d pos4(backdropRect.left-0.5f, backdropRect.bottom-0.5f, 1.0f);
            graphics->setScreenSpaceVertex(pos4);

            m::point4d pos3(backdropRect.right-0.5f, backdropRect.bottom-0.5f, 1.0f);
            graphics->setScreenSpaceVertex(pos3);

            graphics->end();

            effect->endPass();
        }

        effect->end(graphics);

        // draw the fps text

        static const g::packed_color textColor = g::packColor(255,198,198,198);
        textRect.right -= 2;
        textRect.top -= 2;
        _font->renderText(
            graphics,
            ws.c_str(),
            textColor,
            g::horizontal_alignment::left,
            g::vertical_alignment::top,
            &textRect
            );
    }

    void renderFloor(const int32 tileCount, const float32 tileGap)
    {
        std::shared_ptr<g::iengine> graphics = _graphics.lock();

        float32 depth = tileGap * tileCount;

        graphics->setTexture(0, std::shared_ptr<g::itexture>());

        std::shared_ptr<g::ieffect> effect;
        g::handle technique;
        effect = graphics->getStandardEffect(g::standard_effect::colored, &technique);
    
        uint32 passes = 0;
        effect->setTechnique(technique);
        passes = effect->begin(graphics);

        graphics->setZBuffer(g::enabled_state::enabled);
        graphics->setZCompare(g::compare_state::less);

        // Render floor
        for(uint32 p = 0; p < passes; p++)
        {
            effect->beginPass(p);
    
            graphics->begin(g::primitive_type::lineList);

            g::packed_color color = g::packColor(64, 255, 255 ,255);

            float32 halfDepth = depth / 2;

            m::point3d vec1(halfDepth * -1, halfDepth * -1, 0);
            m::point3d vec2(halfDepth * 1, halfDepth * -1, 0);

            m::point3d vec3(halfDepth * -1, halfDepth * -1, 0);
            m::point3d vec4(halfDepth * -1, halfDepth * 1, 0);

            graphics->setColor(color);

            for (int32 i = 0; i < tileCount; i++)
            {
                graphics->setVertex(vec1);
                graphics->setVertex(vec2);

                vec1.y += tileGap;
                vec2.y += tileGap;

                graphics->setVertex(vec3);
                graphics->setVertex(vec4);

                vec3.x += tileGap;
                vec4.x += tileGap;
            }
    
            graphics->end();
    
            effect->endPass();
        }

        m::point3d origin;
        float32 scale = 2.0f;
        m::point3d xVec(scale, 0, 0);
        m::point3d yVec(0, scale, 0);
        m::point3d zVec(0, 0, scale);
        static const g::packed_color xColor = g::packColor(64,255,64,64);
        static const g::packed_color yColor = g::packColor(64,64,255,64);
        static const g::packed_color zColor = g::packColor(64,64,64,255);

        passes = 0;
        effect->setTechnique(technique);
        passes = effect->begin(graphics);

        // Render origin
        graphics->setZBuffer(g::enabled_state::disabled);
        graphics->setZCompare(g::compare_state::never);

        for(uint32 p = 0; p < passes; p++)
        {
            effect->beginPass(p);
    
            graphics->begin(g::primitive_type::lineList);

            float32 halfDepth = depth / 2;

            for (int32 i = 0; i < tileCount; i++)
            {
                graphics->setColor(xColor);
                graphics->setVertex(origin);
                graphics->setVertex(xVec);

                graphics->setColor(yColor);
                graphics->setVertex(origin);
                graphics->setVertex(yVec);

                graphics->setColor(zColor);
                graphics->setVertex(origin);
                graphics->setVertex(zVec);
            }
    
            graphics->end();
    
            effect->endPass();
        }
   
        effect->end(graphics);
    }

    void renderAxes()
    {
        std::shared_ptr<g::iengine> graphics = _graphics.lock();

        g::display_params dp;
        ZeroMemory(&dp, sizeof(dp));

        dp = graphics->getDisplayParams();

        float32 aspect = 1;

        g::viewport vp;
        graphics->getViewport(&vp);
        float32 windowWidth = vp.width;
        float32 windowHeight = vp.height;

        m::point3d origin;
        m::matrix44 world;
        m::matrix44 camCopy = _camera.lock()->matrix();

        // Note, JORGEPER: this is a bit trick, we are trying to render the coordinate system axes
        // as if the camera was placed in the center of the "real" coordinate system (0,0,0)
        // so we take the camera to the center, then get the "fake" coordinate system's vectors and render them
        // using an orthographic projection (with some offsets/scales to place them at the bottom-right corner of the screen)
        // The trick is that it's setting the camera to identity and having the world be rotated as the inverse (transpose)
        // of the original camera.  Got this code from digit.
        camCopy.setTranslation(origin);
        camCopy.getTranspose(&world);
        
        m::matrix44 ident;
        m::point3d transScale;
        
        if (windowWidth > windowHeight)
        {
            aspect = (float32)windowWidth / windowHeight;
            transScale = m::point3d(aspect * 0.9f, -0.85f, 0.5f);
        }
        else
        {
            aspect = (float32)windowHeight / windowWidth;
            transScale = m::point3d(0.85f, aspect * -0.9f, 0.5f);
        }

        m::point3d translation;

        m::vector3d rightVec;
        m::vector3d fwdVec;
        m::vector3d upVec;
        m::matrix44 coordSystemMatrix = graphics->getCoordinateSystemMatrix();
        coordSystemMatrix.getRightVector(&rightVec);
        coordSystemMatrix.getForwardVector(&fwdVec);
        coordSystemMatrix.getUpVector(&upVec);
        
        rightVec.scale(transScale.x);
        upVec.scale(transScale.y);
        fwdVec.scale(transScale.z);
        m::vector3d t;
        rightVec.add(upVec, &t);
        translation = t;
        translation.offsetBy(fwdVec, &translation);

        world.setTranslation(translation);

        graphics->pushWorldMatrix(world, false);
        if (windowWidth > windowHeight)
        {
            graphics->pushOrthographicProjection(-aspect, aspect, 1, -1, 0.1f, 1);
        }
        else
        {
            graphics->pushOrthographicProjection(-1, 1, aspect, -aspect, 0.1f, 1);
        }
        graphics->pushCameraMatrix(ident);

        std::shared_ptr<g::iscene_transformer> transformer = graphics->snapshotCurrentSceneTransforms();

        graphics->setTexture(0, std::shared_ptr<g::itexture>());

        std::shared_ptr<g::ieffect> effect;
        g::handle technique;
        effect = graphics->getStandardEffect(g::standard_effect::colored, &technique);

        const float32 axisScale = 0.1f;
        m::point3d xVec(axisScale, 0, 0);
        m::point3d yVec(0, axisScale, 0);
        m::point3d zVec(0, 0, axisScale);

        m::point3d orig(0, 0, 0);

        static const g::packed_color xColor = g::packColor(255,64,64);
        static const g::packed_color yColor = g::packColor(64,255,64);
        static const g::packed_color zColor = g::packColor(64,64,255);

        graphics->setZBuffer(g::enabled_state::disabled);
        graphics->setCulling(g::cull_state::none);

        uint32 passes = 0;
        effect->setTechnique(technique);
        passes = effect->begin(graphics);
        for(uint32 p = 0; p < passes; p++)
        {
            effect->beginPass(p);

            graphics->begin(g::primitive_type::lineList);
        
            graphics->setColor(xColor);
            graphics->setVertex(orig);
            graphics->setVertex(xVec);

            graphics->setColor(yColor);
            graphics->setVertex(orig);
            graphics->setVertex(yVec);

            graphics->setColor(zColor);
            graphics->setVertex(orig);
            graphics->setVertex(zVec);

            graphics->end();

            effect->endPass();
        }

        effect->end(graphics);

        // draw axis labels
        static const g::packed_color black = g::packColor(255,0,0,0);
        m::point3d worldPoint;
        m::point2d pixelPoint;
        m::rectangle pixelRect;
        float32 z;

        // X label (with drop shadow drawn first)
        world.apply(xVec, &worldPoint);
        pixelPoint = transformer->worldSpaceToPixel(worldPoint, &z);

        pixelRect.set(pixelPoint.x, pixelPoint.y, pixelPoint.x, pixelPoint.y);
        _font->renderText(
            graphics,
            L"X",
            xColor,
            g::horizontal_alignment::center,
            g::vertical_alignment::center,
            &pixelRect
            );

        // Y label (with drop shadow drawn first)
        world.apply(yVec, &worldPoint);
        pixelPoint = transformer->worldSpaceToPixel(worldPoint, &z);
        
        pixelRect.set(pixelPoint.x, pixelPoint.y, pixelPoint.x, pixelPoint.y);
        _font->renderText(
            graphics,
            L"Y",
            yColor,
            g::horizontal_alignment::center,
            g::vertical_alignment::center,
            &pixelRect
            );

        // Z label (with drop shadow drawn first)
        world.apply(zVec, &worldPoint);
        pixelPoint = transformer->worldSpaceToPixel(worldPoint, &z);

        pixelRect.set(pixelPoint.x, pixelPoint.y, pixelPoint.x, pixelPoint.y);
        _font->renderText(
            graphics,
            L"Z",
            zColor,
            g::horizontal_alignment::center,
            g::vertical_alignment::center,
            &pixelRect
            );

        graphics->popProjectionMatrix();
        graphics->popWorldMatrix();
        graphics->popCameraMatrix();
    }

private:
    void initialize(
        const std::shared_ptr<icamera>& camera,
        const wchar* fontName,
        const int fontSize
        )
    {
        // cache these
        _renderService = services::get<g::irender_service>();
        _graphics = services::get<g::iengine>();

        std::shared_ptr<g::iasset_manager> assetManager = _graphics.lock()->getAssetManager();

        _font = assetManager->getOrCreateFont(
            _graphics.lock(), 
            fontName,
            fontSize,
            false // right to left
            );

        _camera = camera;
    }

private:
    std::weak_ptr<icamera> _camera;
    std::shared_ptr<g::ifont> _font;
    std::weak_ptr<g::iengine> _graphics;
    std::weak_ptr<g::irender_service> _renderService;
};

}}